#include "pch.h"
#include "TiledPreviewRenderer.h"

#include "DXHelper.h"
#include "NativeBuffer.h"

using namespace Concurrency;
using namespace DirectX;
using namespace Nokia::Graphics::Imaging;
using namespace Windows::Foundation;

TiledPreviewRenderer::TiledPreviewRenderer() :
	m_initialized(FALSE),
	m_resolution(0.0F, 0.0F),
	m_pDevice(NULL),
	m_pContext(NULL),
	m_pInputLayout(NULL),
	m_pVertexBuffer(NULL),
	m_pVertexShader(NULL),
	m_pPixelShader(NULL),
	m_pPreviewTextureSampler(NULL),
	m_pPreviewTexture(NULL),
	m_pPreviewTextureView(NULL)
{
	m_pVertexData[0].pos = XMFLOAT2(-1.0F, 1.0F);
	m_pVertexData[1].pos = XMFLOAT2(1.0F, 1.0F);
	m_pVertexData[2].pos = XMFLOAT2(-1.0F, -1.0F);
	m_pVertexData[3].pos = XMFLOAT2(1.0F, -1.0F);
}

void TiledPreviewRenderer::CreateDeviceResources(ID3D11Device1* pDevice)
{
	m_initialized = FALSE;

	if (m_pDevice)
	{
		ReleaseDeviceResources();
	}

	if (pDevice)
	{
		m_pDevice = pDevice;
		m_pDevice->AddRef();

		task<Platform::Array<BYTE, 1U>^> loadVSTask = DX::ReadDataAsync("VertexShader.cso");
		task<Platform::Array<BYTE, 1U>^> loadPSTask = DX::ReadDataAsync("PixelShader.cso");

		task<void> createVSTask = loadVSTask.then([this](Platform::Array<BYTE, 1U>^ arg)
		{
			CD3D11_BUFFER_DESC vertexBufferDesc = CD3D11_BUFFER_DESC(sizeof(VERTEX_POSITION_COORD) * 4U, D3D11_BIND_VERTEX_BUFFER);
			const D3D11_INPUT_ELEMENT_DESC inputElementDesc[] =
			{
				{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
				{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0 },
			};

			HRESULT hr = m_pDevice->CreateVertexShader(arg->Data, arg->Length, NULL, &m_pVertexShader);
			if (SUCCEEDED(hr))
			{
				hr = m_pDevice->CreateInputLayout(
					inputElementDesc,
					ARRAYSIZE(inputElementDesc),
					arg->Data,
					arg->Length,
					&m_pInputLayout
					);
			}
			if (SUCCEEDED(hr))
			{
				hr = m_pDevice->CreateBuffer(&vertexBufferDesc, NULL, &m_pVertexBuffer);
			}

			DX::ThrowIfFailed(hr);
		});

		task<void> createPSTask = loadPSTask.then([this](Platform::Array<BYTE, 1U>^ arg)
		{
			HRESULT hr = m_pDevice->CreatePixelShader(arg->Data, arg->Length, NULL, &m_pPixelShader);

			DX::ThrowIfFailed(hr);
		});

		(createPSTask && createVSTask).then([this]()
		{
			m_initialized = TRUE;
		});
	}
}

void TiledPreviewRenderer::CreatePreviewTexture()
{
	if (m_pPreviewTexture)
	{
		ReleasePreviewTexture();
	}

	CD3D11_TEXTURE2D_DESC textureDesc = CD3D11_TEXTURE2D_DESC(
		DXGI_FORMAT_B8G8R8A8_UNORM,
		m_previewTextureSize.x,
		m_previewTextureSize.y,
		1U,
		1U,
		D3D11_BIND_SHADER_RESOURCE,
		D3D11_USAGE_DYNAMIC,
		D3D11_CPU_ACCESS_WRITE,
		1U,
		0U,
		0U
		);

	HRESULT hr = m_pDevice->CreateTexture2D(&textureDesc, nullptr, &m_pPreviewTexture);
	if (SUCCEEDED(hr))
	{
		hr = m_pDevice->CreateShaderResourceView(m_pPreviewTexture, nullptr, &m_pPreviewTextureView);
	}

	DX::ThrowIfFailed(hr);
}

void TiledPreviewRenderer::ReleaseDeviceResources()
{
	SafeRelease(&m_pDevice);

	SafeRelease(&m_pVertexShader);
	SafeRelease(&m_pInputLayout);

	SafeRelease(&m_pPixelShader);
	SafeRelease(&m_pPreviewTextureSampler);

	ReleasePreviewTexture();
}

void TiledPreviewRenderer::ReleasePreviewTexture()
{
	SafeRelease(&m_pPreviewTexture);
	SafeRelease(&m_pPreviewTextureView);
}

BOOL TiledPreviewRenderer::CheckForDeviceChange(ID3D11Device1 *pDevice)
{
	BOOL result = FALSE;

	if (m_pDevice != pDevice)
	{
		CreateDeviceResources(pDevice);
		result = TRUE;
	}

	return result;
}

void TiledPreviewRenderer::UpdateForPropertyChange(
	Windows::Foundation::Size resolution,
	Windows::Foundation::Rect rect,
	Nokia::Graphics::Imaging::Rotation rotation,
	Nokia::Graphics::Imaging::FlipMode flip,
	ID3D11DeviceContext1 *pContext
	)
{
	const XMFLOAT2 normalCoords[4] =
	{
		XMFLOAT2(rect.Left / resolution.Width, rect.Top / resolution.Height),
		XMFLOAT2(rect.Right / resolution.Width, rect.Top / resolution.Height),
		XMFLOAT2(rect.Right / resolution.Width, rect.Bottom / resolution.Height),
		XMFLOAT2(rect.Left / resolution.Width, rect.Bottom / resolution.Height)
	};

	for (UINT32 i = 0; i < 4; i++)
	{
		m_pVertexData[i].uv = normalCoords[(i + 4 - (UINT32) rotation) % 4];
	}

	if ((flip & Nokia::Graphics::Imaging::FlipMode::Vertical) == Nokia::Graphics::Imaging::FlipMode::Vertical)
	{
		XMFLOAT2 swap;

		swap = m_pVertexData[0].uv;
		m_pVertexData[0].uv = m_pVertexData[1].uv;
		m_pVertexData[1].uv = swap;

		swap = m_pVertexData[3].uv;
		m_pVertexData[3].uv = m_pVertexData[2].uv;
		m_pVertexData[2].uv = swap;
	}

	if ((flip & Nokia::Graphics::Imaging::FlipMode::Horizontal) == Nokia::Graphics::Imaging::FlipMode::Horizontal)
	{
		XMFLOAT2 swap;

		swap = m_pVertexData[0].uv;
		m_pVertexData[0].uv = m_pVertexData[3].uv;
		m_pVertexData[3].uv = swap;

		swap = m_pVertexData[1].uv;
		m_pVertexData[1].uv = m_pVertexData[2].uv;
		m_pVertexData[2].uv = swap;
	}

	pContext->UpdateSubresource(m_pVertexBuffer, 0U, NULL, m_pVertexData, 0U, 0U);
}

void TiledPreviewRenderer::Update(Nokia::Graphics::Imaging::IImageProvider^ source, ID3D11DeviceContext1* pContext)
{
	create_task(source->GetInfoAsync()).then([=](ImageProviderInfo^ info)
	{
		XMUINT2 desiredSize = XMUINT2(
			(UINT32) (info->ImageSize.Width + 0.5F),
			(UINT32) (info->ImageSize.Height + 0.5F)
			);

		if (desiredSize.x != m_previewTextureSize.x || desiredSize.y != m_previewTextureSize.y || !m_pPreviewTexture)
		{
			m_previewTextureSize = desiredSize;
			CreatePreviewTexture();
		}
	}).then([=]()
	{
		D3D11_MAPPED_SUBRESOURCE subresource = { 0 };

		pContext->Map(m_pPreviewTexture, 0U, D3D11_MAP_WRITE_DISCARD, 0U, &subresource);
		Bitmap^ bitmap = ref new Bitmap(
			Size((FLOAT) m_previewTextureSize.x, (FLOAT) m_previewTextureSize.y),
			ColorMode::Bgra8888,
			4U * m_previewTextureSize.x,
			NativeBuffer::CreateNativeBuffer((BYTE *) subresource.pData, 4U * m_previewTextureSize.x * m_previewTextureSize.y)
			);

		return source->GetBitmapAsync(bitmap, OutputOption::PreserveAspectRatio);
	}).then([=](Bitmap^ bitmap)
	{
		pContext->Unmap(m_pPreviewTexture, 0U);
	}).wait();
}

void TiledPreviewRenderer::Render(ID3D11Device1 *pDevice, ID3D11DeviceContext1 *pContext)
{
	if (m_initialized)
	{
		UINT32 vertexBufferStride = sizeof(VERTEX_POSITION_COORD);
		UINT32 vertexBufferOffset = 0U;

		pContext->IASetInputLayout(m_pInputLayout);
		pContext->IASetVertexBuffers(0U, 1U, &m_pVertexBuffer, &vertexBufferStride, &vertexBufferOffset);
		pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);

		pContext->VSSetShader(m_pVertexShader, NULL, 0U);

		pContext->PSSetShader(m_pPixelShader, NULL, 0U);
		pContext->PSSetSamplers(0U, 1U, &m_pPreviewTextureSampler);
		pContext->PSSetShaderResources(0U, 1U, &m_pPreviewTextureView);

		pContext->Draw(4U, 0U);
	}
}